#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "ringbuffer.h"

#ifdef __cplusplus
extern "C" {
#endif

struct _ringbuffer_t{
    uint32_t head;
    uint32_t tail;
    uint32_t size;
    uint8_t buffer[0];
};

ringbuffer_t *ringbuffer_create(uint16_t size)
{
    uint8_t *ptr = NULL;
    ringbuffer_t *rb = NULL;
    if(0 == size)
    {
        errno = EINVAL;
        return NULL;
    }

    ptr = (uint8_t *)malloc(sizeof(ringbuffer_t) + size + 1);
    if(NULL == ptr)
    {
        errno = ENOMEM;
        return NULL;
    }

    rb = (ringbuffer_t *)ptr;
    rb->size = size + 1;
    rb->head = rb->tail = 0;

    return rb;
}

void ringbuffer_destroy(ringbuffer_t *rb)
{
    if(rb)
        free(rb);
}

int ringbuffer_empty(ringbuffer_t *rb)
{
    if(NULL == rb)
    {
        errno = EINVAL;
        return -1;
    }
    return (rb->head == rb->tail) ? 1 : 0;
}

int ringbuffer_full(ringbuffer_t *rb)
{
    if(NULL == rb)
    {
        errno = EINVAL;
        return -1;
    }
    return (rb->head == ((rb->tail + 1) % rb->size)) ? 1 : 0;
}

int ringbuffer_used(ringbuffer_t *rb)
{
    if(NULL == rb)
    {
        errno = EINVAL;
        return -1;
    }
   return ((rb->size + rb->tail - rb->head) % rb->size); 
}

int ringbuffer_remain(ringbuffer_t *rb)
{
    if(NULL == rb)
    {
        errno = EINVAL;
        return -1;
    }
    if(ringbuffer_empty(rb))
        return rb->size - 1;
    return ((rb->size + rb->head - rb->tail) % rb->size) - 1;
}

void ringbuffer_clear(ringbuffer_t *rb)
{
    if(rb)
        rb->head = rb->tail = 0;
}

int ringbuffer_put(ringbuffer_t *rb, uint8_t *ptr, int len)
{
    int i = 0;
    if((NULL == rb) || (NULL == ptr) || (0 == len))
    {
        errno = EINVAL;
        return -1;
    }

    if(ringbuffer_remain(rb) < len)
    {
        errno = EXFULL;
        return -1;
    }
    for(i = 0; i < len; i++)
        rb->buffer[(rb->tail + i) % rb->size] = ptr[i];
    rb->tail = (rb->tail + len) % rb->size;

    return len;
}

int ringbuffer_put_cover(ringbuffer_t *rb, uint8_t *ptr, int len)
{
    int i, cp_len = ringbuffer_remain(rb);
    if((NULL == rb) || (NULL == ptr) || (0 == len))
    {
        errno = EINVAL;
        return -1;
    }

    for(i = 0; i < len; i++)
        rb->buffer[(rb->tail + i) % rb->size] = ptr[i];
    rb->tail = (rb->tail + len) % rb->size;
    if(cp_len < len)
        rb->head = (rb->head + len - cp_len) % rb->size;

    return len;
}

int ringbuffer_get(ringbuffer_t *rb, uint8_t *ptr, int len)
{
    int i, cp_len = ringbuffer_used(rb);
    if((NULL == rb) || (NULL == ptr) || (0 == len))
    {
        errno = EINVAL;
        return -1;
    }

    cp_len = cp_len > len ? len : cp_len;
    for(i = 0; i < cp_len; i++)
        ptr[i] = rb->buffer[(rb->head + i) % rb->size];
    rb->head = (rb->head + cp_len) % rb->size;

    return cp_len;
}

#ifdef __cplusplus
}
#endif
